#include "primitive.h"

#include <cstdio>
#include <cmath>

#include "models.h"

/////////////
//  Point  //
/////////////

Point::Point(){            
    this->x = 0.0;
    this->y = 0.0;
    this->z = 0.0;
}

Point::Point(float _x, float _y, float _z){
    this->x = _x;
    this->y = _y;
    this->z = _z;
}

Point::~Point(){

}
    
void Point::setX(float x){
    this->x = x;
}

float Point::getX(){
    return this->x;
}

void Point::setY(float y){
    this->y = y;
}

float Point::getY(){
    return this->y;
}

void Point::setZ(float z){
    this->z = z;
}

float Point::getZ(){
    return this->z;
}        

void Point::show(){
    printf("(%f,   %f,   %f)\n", x, y, z);
}   

float Point::lenght(){
    return sqrt( pow(x, 2) + pow(y, 2) + pow(z, 2));
}

float Point::distance(Point &p){
    return sqrt( pow(p.getX() - this->x, 2) + 
                 pow(p.getY() - this->y, 2) + 
                 pow(p.getZ() - this->z, 2) );
}

bool Point::operator==(Point &p){
    if (distance(p) > -0.1 and distance(p) < 0.1){
        return true;
    }
    else{
        return false;
    }
}

void Point::operator+=(Point p){
    this->x += p.getX();
    this->y += p.getY();
    this->z += p.getZ();
}

Point Point::operator+(Point p){
    Point r;
    r.setX( this->x + p.getX() );
    r.setY( this->y + p.getY() );
    r.setZ( this->z + p.getZ() );
    return r;
}

void Point::operator-=(Point p){
    this->x -= p.getX();
    this->y -= p.getY();
    this->z -= p.getZ();
}
        
Point Point::operator-(Point p){
    Point r;
    r.setX( this->x - p.getX() );
    r.setY( this->y - p.getY() );
    r.setZ( this->z - p.getZ() );
    return r;
}        

void Point::operator*=(float h){
    this->x *= h;
    this->y *= h;
    this->z *= h;
}

Point Point::operator*(float h){
    Point r;
    r.setX( this->x * h );
    r.setY( this->y * h );
    r.setZ( this->z * h );
    return r;
}

void Point::operator/=(float h){
    this->x /= h;
    this->y /= h;
    this->z /= h;
}

Point Point::operator/(float h){
    Point r;
    r.setX( this->x / h );
    r.setY( this->y / h );
    r.setZ( this->z / h );
    return r;
}

Point Point::crossProduct(Point &p){
    return Point( this->y * p.getZ() - this->z * p.getY(),
                  this->z * p.getX() - this->x * p.getZ(),
                  this->x * p.getY() - this->y * p.getX() );
}


float Point::dotProduct(Point &p){
    return this->x * p.getX() + this->y * p.getY() + this->z * p.getZ();
}

Point Point::normalize(){
    return this->operator/( lenght() );
}


/////////////
//  Model  //
/////////////

void Model::drawSphere(float radius, int slices, int stacks){
    GLUquadricObj *pObj;
    pObj = gluNewQuadric();
    gluQuadricNormals(pObj, GLU_SMOOTH);
    gluSphere(pObj, radius, slices, stacks);
    gluDeleteQuadric(pObj);
}


void Model::drawSphere(Point p, float radius){
    glPushMatrix();
        glTranslatef(p.getX(), p.getY(), p.getZ());
        Model::drawSphere(radius);
    glPopMatrix();
}

void Model::drawSegment(Point origin, Point destination){
    glPushMatrix();

    Model::drawSphere(origin, 9.0);
    Model::drawSphere(destination, 9.0);

    glBegin(GL_LINES);
        glVertex3f( origin.getX(), origin.getY(), origin.getZ() );
        glVertex3f( destination.getX(), destination.getY(), destination.getZ() );
    glEnd();
    
    glPopMatrix();
}

/////////////////
//  Primitive  //
/////////////////

Primitive::Primitive(){}
Primitive::~Primitive(){}

int Primitive::getParentId(){
    return this->id;
}

int Primitive::getId(){
    return this->id;
}

void Primitive::setColor(float _r, float _g, float _b, float _a){
    this->r = _r;
    this->g = _g;
    this->b = _b;
    this->a = _a;
}

float Primitive::getRed(){
    return this->r;
}

float Primitive::getGreen(){
    return this->g;
}

float Primitive::getBlue(){
    return this->b;
}

float Primitive::getAlpha(){
    return this->a;
}

void Primitive::setSize(int s){
    if (s > 0 and s < 10){
        this->size = s;
    }
}

int Primitive::getSize(){
    return this->size;
}

void Primitive::setRotateX(float rx){
    this->rotate_x = rx;
}

float Primitive::getRotateX(){
    return this->rotate_x;
}

void Primitive::setRotateY(float ry){
    this->rotate_y = ry;
}

float Primitive::getRotateY(){
    return this->rotate_y;
}

void Primitive::setRotateZ(float rz){
    this->rotate_z = rz;
}

float Primitive::getRotateZ(){
    return this->rotate_z;
}  

void Primitive::rotate(){
    glRotatef(this->rotate_x, 1.0, 0.0, 0.0);
    glRotatef(this->rotate_y, 0.0, 1.0, 0.0);
    glRotatef(this->rotate_z, 0.0, 0.0, 1.0);
}

void Primitive::rotate(float angle, Point direction){
    glRotatef(angle, direction.getX(), direction.getY(), direction.getZ());
}            

void Primitive::setTranslation(float tx, float ty, float tz){
    this->translate_x = tx;
    this->translate_y = ty;
    this->translate_z = tz;
}
void Primitive::setTranslateX(float tx){
    this->translate_x = tx;
}

float Primitive::getTranslateX(){
    return this->translate_x;
}

void Primitive::setTranslateY(float ty){
    this->translate_y = ty;
}

float Primitive::getTranslateY(){
    return this->translate_y;
}

void Primitive::setTranslateZ(float tz){
    this->translate_z = tz;
}

float Primitive::getTranslateZ(){return this->translate_z;} 


void Primitive::translate(){
    glTranslatef(this->translate_x, this->translate_y, this->translate_z);
} 

void Primitive::setScaleX(float sx){
    this->scale_x = sx;
}

float Primitive::setScaleX(){
    return this->scale_x;
}

void Primitive::setScaleY(float sy){
    this->scale_y = sy;
}

float Primitive::setScaleY(){
    return this->scale_y;
}

void Primitive::setScaleZ(float sz){
    this->scale_z = sz;
}

float Primitive::setScaleZ(){
    return this->scale_z;
}

void Primitive::transforms(){
    rotate();
    translate();
}

//////////////
//  Vertex  //
//////////////

Vertex::Vertex(unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = VERTEX;
    
    this->x = 0.0;
    this->y = 0.0;
    this->z = 0.0;
    
    this->r = 0.0;
    this->g = 1.0;
    this->b = 0.0;
    this->a = 1.0;
    
    this->size = 9;
}    

Vertex::Vertex(float _x, float _y, unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = VERTEX;
    
    this->x = _x;
    this->y = _y;
    this->z = 0.0;
}      

Vertex::Vertex(float _x, float _y, float _z, unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = VERTEX;
    
    this->x = _x;
    this->y = _y;
    this->z = _z;
}        

Vertex::~Vertex(){

}

bool Vertex::isEmpty(){
    if ( x == 0.0 and y == 0.0 and z == 0.0 ){
        return true;
    }
    else{
        return false;
    }
}

void Vertex::draw(bool showVertex){
    if (showVertex){
        glColor4f(this->r, this->g, this->b, this->a);
        glLoadName(this->id);
        glPushMatrix();
            glTranslatef(this->x, this->y, this->z);
            Model::drawSphere(float (this->size) / 25.0);
        glPopMatrix();
    }
}

////////////
//  Edge  //
////////////

Edge::Edge(Vertex v1, Vertex v2, unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = EDGE;
    
    this->vertices.first = v1;
    this->vertices.second = v2;
    
    this->r = 1.0;
    this->g = 0.0;
    this->b = 0.0;
    this->a = 1.0;
    
    this->size = 10;
}    

Edge::Edge(unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = EDGE;
}

Edge::~Edge(){

}

bool Edge::isEmpty(){
    if ( vertices.first.isEmpty() and vertices.second.isEmpty()){
        return true;
    }
    else{
        return false;
    }
}

void Edge::show(){
    printf("\n\t\tEdge: %i\n",this->id);
    printf("\t\t\t");this->vertices.first.show();
    printf("\t\t\t");this->vertices.second.show();
}

void Edge::setVertices(Vertex v1, Vertex v2){
    this->vertices.first = v1;
    this->vertices.second = v2;
}

pair<Vertex, Vertex>& Edge::getVertices(){
    return vertices;
}

void Edge::draw(bool showEdge, bool showVertices){
    if (showEdge){
        glColor4f(this->r, this->g, this->b, this->a);
        glLineWidth(this->size);                
        glLoadName(this->id);

        Cylinder c(vertices.first, 8.0/25.0, vertices.second-vertices.first);
        c.draw();
        vertices.first.draw(showVertices);
    }
}


////////////
//  Face  //
////////////

Face::Face(unsigned int _id, unsigned int _parentId){
    this->id = _id;
    this->parentId = _parentId;
    
    this->type = FACE;
    
    this->r = 0.0;
    this->g = 0.5;
    this->b = 0.5;
    this->a = 1.0;        
}

Face::~Face(){}

bool Face::isEmpty(){
    return edges.empty();
}

void Face::show(){
    printf("\n\tFace: %i",this->id);
    for (int i = 0; i < this->edges.size(); i++){
        edges[i].show();
    }
}

void Face::addEdge(Edge e){
    this->edges.push_back(e);
}

bool Face::hasEdge(int _id){
    for (int i = 0; i < this->edges.size(); i++){
        if (edges[i].getId() == _id){
            return true;
        }
    }
    return false;
}

Edge& Face::getEdge(unsigned int _id){
    for (int i = 0; i < this->edges.size(); i++){
        if (edges[i].getId() == _id){
            return edges[i];
        }
    }
}


vector<Edge>& Face::getEdges(){
    return edges;
}

void Face::draw(bool showFace, bool showEdges, bool showVertices){
    if (showFace){
        Polygon poly;
        Point added, p1, p2;
    
        glColor4f(this->r, this->g, this->b, this->a);
        glLoadName(this->id);
        
        added = Point( edges[0].getVertices().first.getX(),
                       edges[0].getVertices().first.getY()+0.001,
                       edges[0].getVertices().first.getZ() );
        for (unsigned int j = 0; j < edges.size(); j++){
            p1 = Point( edges[j].getVertices().first.getX(),
                        edges[j].getVertices().first.getY()+0.001,
                        edges[j].getVertices().first.getZ() );
                     
            p2 = Point( edges[j].getVertices().second.getX(),
                        edges[j].getVertices().second.getY()+0.001,
                        edges[j].getVertices().second.getZ() );
            
            if (added == p1){
                poly.addVertex(p2);
                added = p2;
            }
            else{
                poly.addVertex(p1);
                added = p1;
            }
        }
        poly.draw(this->r, this->g, this->b);
    }
    
    for (unsigned int j = 0; j < edges.size(); j++){
        edges[j].draw(showEdges, showVertices);
    }
}


//////////////
//  Object  //
//////////////

Object::Object(unsigned int _id){
    this->id = _id;
    this->parentId = 0;
    
    this->type = OBJECT;
}


Object::~Object(){

}

bool Object::isEmpty(){
    if ( faces.size() == 0){
        return true;
    }
    else{
        return false;
    }
}

void Object::show(){
    printf("\n\nOBJECT: %i",this->id);
    for (int i = 0; i < faces.size(); i++){
        faces[i].show();
    }
}

void Object::addFace(Face &f){
    faces.push_back(f);
}

bool Object::hasFace(int _id){
     for (unsigned int i = 0; i < this->faces.size(); i++){
        if (faces[i].getId() == _id){
            return true;
        }
     }
     return false;
}        

Face& Object::getFace(unsigned int _id){
     for (unsigned int i = 0; i < this->faces.size(); i++){
        if (faces[i].getId() == _id){
            return faces[i];
        }
     }
}    


bool Object::hasEdge(int _id){
    for (unsigned int i = 0; i < this->faces.size(); i++){
        if (faces[i].hasEdge(_id)){
            return true;
        }
    }
    return false;
}

Edge& Object::getEdge(unsigned int _id){
    for (unsigned int i = 0; i < this->faces.size(); i++){
        if (faces[i].hasEdge(_id)){
            return faces[i].getEdge(_id);
        }
    }
}

bool Object::hasVertex(int _id){
    for (unsigned int i = 0; i < this->faces.size(); i++){
        vector<Edge> &eds = faces[i].getEdges();
        for (unsigned int j = 0; j < eds.size(); j++){
            pair<Vertex, Vertex> &vertices = eds[j].getVertices();
            if (vertices.first.getId() == _id){
                return true;
            } 
        }
    }
    return false;
}

Vertex& Object::getVertex(unsigned int _id){
    for (unsigned int i = 0; i < this->faces.size(); i++){
        vector<Edge> &eds = faces[i].getEdges();
        for (unsigned int j = 0; j < eds.size(); j++){
            pair<Vertex, Vertex> &vertices = eds[j].getVertices();
            if (vertices.first.getId() == _id){
                return vertices.first;
            }                
        }
    }
}

vector<Face>& Object::getFaces(){
    return faces;
}

void Object::draw(bool showFaces, bool showEdges, bool showVertices){
    for (unsigned int i = 0; i < faces.size(); i++){
        faces[i].draw(showFaces, showEdges, showVertices);
    }
}


/////////////////////
//  ObjectFactory  //
/////////////////////

ObjectFactory::ObjectFactory(){
    this->objectId = 0;
    this->faceId = 2000;
    this->edgeId = 1000;
    this->vertexId = 0;
}

ObjectFactory::~ObjectFactory(){

}

Vertex ObjectFactory::createVertex(Point p, unsigned int _parentId){
    vertexId++;
    Vertex v(p.getX(), p.getY(), p.getZ(), _parentId + vertexId, _parentId);
    
    v.setColor(0.0, 1.0, 0.0);
    v.setSize(9);
                
    return v;
}

Vertex ObjectFactory::createVertex(float x, float y, float z, unsigned int _parentId){
    vertexId++;
    Vertex v(x, y, z,  _parentId + vertexId, _parentId);
    
    v.setColor(0.0, 1.0, 0.0);
    v.setSize(9);
                
    return v;            
    
}

Edge ObjectFactory::createEdge(Vertex &v1, Vertex &v2, unsigned int _parentId){
    edgeId++;
    Edge e(v1, v2, _parentId + edgeId, _parentId);
    
    e.setColor(1.0, 0.0, 0.0);
    e.setSize(10);
    
    return e;
}

Face ObjectFactory::createFace(vector<Edge> &edges, unsigned int _parentId){
    faceId++;
    Face face(_parentId + faceId, _parentId);
    face.setColor(0.0, 0.5, 0.5, 1.0);
    
    for (int i = 0; i < edges.size(); i++){
        face.addEdge(edges[i]);
    }
    
    return face;
}

Face ObjectFactory::createFace(unsigned int _parentId){
    faceId++;
    Face face(_parentId + faceId, _parentId);
    return face;
}

Face ObjectFactory::createFace(vector<Point> points, unsigned int _parentId){
    Vertex v1, v2;
    Edge e;
    
    faceId++;
    Face face(_parentId + faceId, _parentId);
    
    v1 = createVertex(points.front(), _parentId);
    for (int i = 1; i < points.size(); i++){
        v2 = createVertex(points[i], _parentId);
        e = createEdge(v1, v2, _parentId);
        face.addEdge(e);
        v1 = v2;
    }
    v2 = createVertex(points.front(), _parentId);
    e = createEdge(v1, v2, _parentId);
    face.addEdge(e);
       
    return face;
}

Object ObjectFactory::createObject(){
    objectId += 10000;
    return Object(objectId);
}

